/**
 * Copyright (C) 2010 David Simonek <dafesimonek@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.dafe.partyband.gui.songs;

import java.awt.Component;
import java.awt.Dialog;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.*;
import org.dafe.partyband.db.api.DbManager;
import org.dafe.partyband.db.api.IBandModelFactory;
import org.dafe.partyband.db.api.ISong;
import org.dafe.partyband.db.api.ISongResource;
import org.dafe.partyband.db.api.handlers.ISongHandler;
import org.dafe.partyband.db.api.handlers.ISongResourceHandler;
import org.dafe.partyband.dochandler.api.DocumentUtils;
import org.dafe.partyband.gui.Utilities;
import org.dafe.partyband.gui.songs.MaterialsTable.MaterialsModel;
import org.dafe.partyband.gui.songs.SongsTable.SongsModel;
import org.dafe.partyband.importer.api.IImporterServices;
import org.dafe.partyband.importer.api.IMimeTypeDetector;
import org.dafe.partyband.importer.api.ResourceTypeUtils;
import org.dafe.partyband.mediaplayer.PlayerTopComponent;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Lookup.Result;
import org.openide.util.*;

/**
 *
 * @author dafe
 */
public class ActionsFactory {

    private static final ISongHandler songHandler = DbManager.getBandManager().getHandlersLookup().lookup(ISongHandler.class);
    private static final ISongResourceHandler resHandler = DbManager.getBandManager().getHandlersLookup().lookup(ISongResourceHandler.class);;
    private static final IMimeTypeDetector mimeDetector = Lookup.getDefault().lookup(IImporterServices.class).getMimeDetector();
    //private static final ISongQuery songQuery = DbManager.getBandManager().getQueriesLookup().lookup(ISongQuery.class);
    private static final IBandModelFactory dbFactory = Lookup.getDefault().lookup(IBandModelFactory.class);
    //private static final INameEncoder nameEncoder = Lookup.getDefault().lookup(IImporterServices.class).getNameEncoder();

    private static final ExternalModificationsSynchronizer externalSyncher = new ExternalModificationsSynchronizer();

    public static Action createOpenMaterialsAction (Lookup lookup) {
        return new OpenMaterialAction(lookup);
    }

    public static Action createPrintMaterialsAction (Lookup lookup, String name, String tooltip) {
        return new PrintMaterialAction(lookup, name, tooltip);
    }

    public static Action createAddMaterialsAction (MaterialsModel materialsModel, JComponent parent) {
        return new AddMaterialsAction(materialsModel, parent);
    }

    public static Action createDeleteMaterialsAction (Lookup selMaterialsLkp,
            MaterialsModel materialsModel, JComponent parent) {
        return new DeleteMaterialsAction(selMaterialsLkp, materialsModel);
    }

    public static Action createPlayMp3MaterialAction (Lookup selMaterialsLkp, Lookup curSongLkp) {
        return new PlayMp3MaterialAction(selMaterialsLkp, curSongLkp);
    }

    public static Action createPlayMp3SongAction (Lookup songLkp) {
        return new PlayMp3SongAction(songLkp);
    }

    public static Action createOpenTextSongAction (Lookup songLkp) {
        return new OpenTextSongAction(songLkp);
    }

    public static Action createDeleteSongsAction (Lookup songLkp, SongsModel songsModel) {
        return new DeleteSongsAction(songLkp, songsModel);
    }

    public static Action createMergeSongsAction (Lookup songLkp, SongsModel songsModel) {
        return new MergeSongsAction(songLkp, songsModel);
    }

    public static Action createFindMaterialsAction (Lookup curSongLkp, MaterialsModel materialsModel, JComponent parent) {
        return new FindMaterialsAction(curSongLkp, materialsModel, parent);
    }
    
    public static Action createEditTableCellAction (Point location, JTable table) {
        return new EditTableCellAction(location, table);
    }

    private static class MergeSongsAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISong> songRes;
        private final SongsModel songsModel;

        public MergeSongsAction(Lookup songLkp, SongsModel songsModel) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_MergeSongs"));
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_MergeSongs"));
            songRes = songLkp.lookupResult(ISong.class);
            songRes.addLookupListener(this);
            this.songsModel = songsModel;

            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            Collection<? extends ISong> songs = songRes.allInstances();
            Iterator<? extends ISong> iter = songs.iterator();
            MergeSongsPanel msp = new MergeSongsPanel(iter.next(), iter.next());
            
            DialogDescriptor dd = new DialogDescriptor(msp,
                    NbBundle.getMessage(SongsTable.class, "MSG_MergeSongsTitle"));
            Dialog dlg = DialogDisplayer.getDefault().createDialog(dd);
            dlg.setVisible(true);
            if (NotifyDescriptor.OK_OPTION.equals(dd.getValue())) {
                ISong sourceSong = msp.getSourceSong();
                ISong targetSong = msp.getTargetSong();

                for (ISongResource curRes : sourceSong.getResources()) {
                    songHandler.addResource(targetSong, curRes, false);
                }
                songsModel.deleteSong(sourceSong, false);
            }

        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled() {
            setEnabled(songRes.allInstances().size() == 2);
        }
    }

    private static class OpenMaterialAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISongResource> songResLkpResult;

        public OpenMaterialAction(Lookup songResLkp) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_OpenMaterials"));
            if (DocumentUtils.isOpenNativeSupported() || DocumentUtils.isOpenInOOfficeSupported()) {
                putValue(Action.SHORT_DESCRIPTION,
                        NbBundle.getMessage(ActionsFactory.class, "HINT_OpenMaterials"));
                songResLkpResult = songResLkp.lookupResult(ISongResource.class);
                setEnabled(songResLkpResult.allInstances().size() == 1);
                songResLkpResult.addLookupListener(this);
            } else {
                putValue(Action.SHORT_DESCRIPTION,
                        NbBundle.getMessage(ActionsFactory.class, "HINT_OpenMaterials_Not_Avail"));
                setEnabled(false);
                songResLkpResult = null;
            }
        }

        public void actionPerformed(ActionEvent e) {
            ISongResource songRes = ((List<ISongResource>)songResLkpResult.allInstances()).get(0);
            File tempFile = exportToTempFile(songRes);
            if (Utilities.isTextType(songRes) && DocumentUtils.isOpenInOOfficeSupported()) {
                DocumentUtils.openInOOffice(tempFile);
            } else {
                DocumentUtils.openNative(tempFile);
            }
            externalSyncher.addSyncPair(songRes, tempFile);
        }

        public void resultChanged(LookupEvent ev) {
            setEnabled((DocumentUtils.isOpenNativeSupported() || DocumentUtils.isOpenInOOfficeSupported())
                    && songResLkpResult.allInstances().size() == 1);
        }
    }

    private static class PrintMaterialAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISongResource> songResLkpResult;

        public PrintMaterialAction(Lookup songResLkp, String name, String tooltip) {
            super(name);
            putValue(Action.SHORT_DESCRIPTION, tooltip);
            songResLkpResult = songResLkp.lookupResult(ISongResource.class);
            songResLkpResult.addLookupListener(this);
            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            Collection<? extends ISongResource> songRescs = songResLkpResult.allInstances();
            for (ISongResource songRes : songRescs) {
                // TODO - support for automatic deleation of temp files
                File tempFile = exportToTempFile(songRes);
                if (Utilities.isTextType(songRes) && DocumentUtils.isOOPrintSupported()) {
                    DocumentUtils.printUsingOOffice(tempFile);
                } else {
                    DocumentUtils.printNative(tempFile);
                }
            }
        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled() {
            boolean result = false;
            Collection<? extends ISongResource> songRes = songResLkpResult.allInstances();
            if (songRes.size() > 0) {
                if (DocumentUtils.isNativePrintSupported()) {
                    result = true;
                } else if (DocumentUtils.isOOPrintSupported()) {
                    result = true;
                    for (ISongResource curRes : songRes) {
                        if (!Utilities.isTextType(curRes)) {
                            result = false;
                            break;
                        }
                    }
                }
            }
            setEnabled(result);
            if (result) {
                putValue(Action.SHORT_DESCRIPTION,
                        NbBundle.getMessage(ActionsFactory.class, "HINT_PrintMaterials"));
            } else {
                putValue(Action.SHORT_DESCRIPTION,
                        NbBundle.getMessage(ActionsFactory.class, "HINT_PrintMaterials_Not_Avail"));
            }
        }
    }

    private static class AddMaterialsAction extends AbstractAction {
        private final MaterialsModel materialsModel;
        private final JComponent parent;

        public AddMaterialsAction(MaterialsModel materialsModel, JComponent parent) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_AddMaterials"));
            this.materialsModel = materialsModel;
            this.parent = parent;
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_AddMaterials"));
        }

        public void actionPerformed(ActionEvent e) {
            JFileChooser fileChooser = new JFileChooser();
            fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            fileChooser.setMultiSelectionEnabled(true);
            fileChooser.setDialogTitle(NbBundle.getMessage(
                    SongsTable.class, "LBL_AddResDialogTitle", materialsModel.getDataSource().getName()));
            int resultOption = fileChooser.showDialog(SwingUtilities.getWindowAncestor(parent),
                    NbBundle.getMessage(SongsTable.class, "LBL_PerformAdd"));

            if (resultOption == JFileChooser.APPROVE_OPTION) {
                File[] files2Add = fileChooser.getSelectedFiles();
                for (int i = 0; i < files2Add.length; i++) {
                    File curFile = files2Add[i];
                    materialsModel.addResource(dbFactory.createSongResource(
                            curFile.getPath(), curFile.length(), curFile.lastModified(),
                            mimeDetector.getMimeType(curFile), null), true);
                }
            }
        }
    }

    private static class FindMaterialsAction extends AbstractAction implements LookupListener {
        private final MaterialsModel materialsModel;
        private final Result<ISong> curSongResult;
        private JComponent parent;

        public FindMaterialsAction(Lookup curSongLkp, MaterialsModel materialsModel, JComponent parent) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_FindMaterials"));
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_FindMaterials"));
            this.materialsModel = materialsModel;
            this.parent = parent;
            this.curSongResult = curSongLkp.lookupResult(ISong.class);
            this.curSongResult.addLookupListener(this);

            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            ISong song = curSongResult.allInstances().iterator().next();
            FindResourcesPanel frp = new FindResourcesPanel(song, parent);
            
            DialogDescriptor dd = new DialogDescriptor(frp,
                    NbBundle.getMessage(SongsTable.class, "MSG_FindMaterialsTitle", song.getName()));
            frp.attachDialogDescriptor(dd);
            Dialog dlg = DialogDisplayer.getDefault().createDialog(dd);
            dlg.setVisible(true);

            if (NotifyDescriptor.OK_OPTION.equals(dd.getValue())) {
                List<File> resources2Add = frp.getMaterials();
                for (File curFile : resources2Add) {
                    materialsModel.addResource(dbFactory.createSongResource(
                            curFile.getPath(), curFile.length(), curFile.lastModified(),
                            mimeDetector.getMimeType(curFile), null), true);
                }
            }

        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled () {
            setEnabled(curSongResult.allInstances().size() == 1);
        }
    }


    private static class DeleteMaterialsAction extends AbstractAction implements LookupListener {
        private final MaterialsModel materialsModel;
        private final Lookup.Result<ISongResource> selMaterialsResult;

        public DeleteMaterialsAction(Lookup selMaterialsLkp, MaterialsModel materialsModel) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_DeleteMaterials"));
            this.selMaterialsResult = selMaterialsLkp.lookupResult(ISongResource.class);
            this.materialsModel = materialsModel;
            selMaterialsResult.addLookupListener(this);
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_DeleteMaterials"));

            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            Object option = DialogDisplayer.getDefault().notify(new NotifyDescriptor.Confirmation(
                    NbBundle.getMessage(SongsTable.class, "MSG_DeleteResource"),
                    NbBundle.getMessage(SongsTable.class, "MSG_DeleteResourceTitle")
            ));
            if (!NotifyDescriptor.OK_OPTION.equals(option)) {
                return;
            }

            Collection<? extends ISongResource> selMaterials = selMaterialsResult.allInstances();
            if (!selMaterials.isEmpty()) {
                for (ISongResource songRes : selMaterials) {
                    materialsModel.deleteResource(songRes);
                }
            }
        }
        
        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled () {
            setEnabled(!selMaterialsResult.allInstances().isEmpty());
        }
    }

    private static class PlayMp3MaterialAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISongResource> selMaterialsResult;
        private ISongResource material;
        private final Lookup.Result<ISong> curSongResult;

        public PlayMp3MaterialAction(Lookup selMaterialsLkp, Lookup curSongLkp) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_PlayMp3Material"));
            this.selMaterialsResult = selMaterialsLkp.lookupResult(ISongResource.class);
            this.curSongResult = curSongLkp.lookupResult(ISong.class);
            selMaterialsResult.addLookupListener(this);
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_PlayMp3Material"));

            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            PlayerTopComponent player = PlayerTopComponent.getDefault();
            player.open();
            player.requestActive();
            player.playSongResource(material, resHandler,
                    curSongResult.allInstances().iterator().next());
        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled () {
            Collection<? extends ISongResource> selMaterials = selMaterialsResult.allInstances();
            if (selMaterials.size() != 1) {
                setEnabled(false);
                return;
            }
            material = selMaterials.iterator().next();
            String mime = material.getMimeType();
            setEnabled(mime != null ? mime.contains("mp3") || mime.contains("mpeg") : false);
        }
    }

    private static File exportToTempFile (ISongResource songRes) {
        File tempFile;
        try {
            tempFile = File.createTempFile("partyband", songRes.getFileName());
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
            return null;
        }
        String origPath = songRes.getFilePath();
        songRes.setFilePath(tempFile.getPath());
        resHandler.exportFromDbToFile(songRes);
        songRes.setFilePath(origPath);

        return tempFile;
    }

    private static class DeleteSongsAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISong> selSongsRes;
        private final SongsModel songsModel;

        public DeleteSongsAction(Lookup selSongsLkp, SongsModel songsModel) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_DeleteSongs"));
            this.selSongsRes = selSongsLkp.lookupResult(ISong.class);
            this.songsModel = songsModel;
            selSongsRes.addLookupListener(this);
        }

        public void actionPerformed(ActionEvent e) {
            Object option = DialogDisplayer.getDefault().notify(new NotifyDescriptor.Confirmation(
                    NbBundle.getMessage(SongsTable.class, "MSG_DeleteSong"),
                    NbBundle.getMessage(SongsTable.class, "MSG_DeleteSongTitle")
            ));
            if (!NotifyDescriptor.OK_OPTION.equals(option)) {
                return;
            }

            for (ISong song : selSongsRes.allInstances()) {
                songsModel.deleteSong(song, true);
            }
        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled() {
            setEnabled(!selSongsRes.allInstances().isEmpty());
        }
    }

    private static class OpenTextSongAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISong> songResult;

        public OpenTextSongAction(Lookup songLkp) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_OpenTextSong"));
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_OpenTextSong"));
            songResult = songLkp.lookupResult(ISong.class);
            songResult.addLookupListener(this);
            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            ISongResource songRes = getTextResource();
            File tempFile = exportToTempFile(songRes);
            if (Utilities.isTextType(songRes) && DocumentUtils.isOpenInOOfficeSupported()) {
                DocumentUtils.openInOOffice(tempFile);
            } else {
                DocumentUtils.openNative(tempFile);
            }
            externalSyncher.addSyncPair(songRes, tempFile);
        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled () {
            setEnabled(songResult.allInstances().size() == 1 &&
                    (DocumentUtils.isOpenNativeSupported() || DocumentUtils.isOpenInOOfficeSupported())
                    && getTextResource() != null);
        }

        private ISongResource getTextResource () {
            Collection<? extends ISong> selSongs = songResult.allInstances();
            if (selSongs.size() == 1) {
                return Utilities.getTextWithChords(selSongs.iterator().next());
            }
            return null;
        }
    }

    private static class PlayMp3SongAction extends AbstractAction implements LookupListener {
        private final Lookup.Result<ISong> songResult;

        public PlayMp3SongAction(Lookup songLkp) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_PlayMp3Song"));
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_PlayMp3Song"));
            this.songResult = songLkp.lookupResult(ISong.class);
            songResult.addLookupListener(this);

            updateEnabled();
        }

        public void actionPerformed(ActionEvent e) {
            PlayerTopComponent player = PlayerTopComponent.getDefault();
            player.open();
            player.requestActive();
            player.playSongResource(getMp3Resource(), resHandler,
                    songResult.allInstances().iterator().next());
        }

        public void resultChanged(LookupEvent ev) {
            updateEnabled();
        }

        private void updateEnabled () {
            setEnabled(getMp3Resource() != null);
        }

        private ISongResource getMp3Resource () {
            Collection<? extends ISong> selSongs = songResult.allInstances();
            if (selSongs.size() == 1) {
                ISong song = selSongs.iterator().next();
                return ResourceTypeUtils.getMp3(song);
            }
            return null;
        }
    }

    private static class EditTableCellAction extends AbstractAction {
        
        private final JTable table;
        private final Point location;

        public EditTableCellAction(Point location, JTable table) {
            super(NbBundle.getMessage(ActionsFactory.class, "ACT_EditTableCell"));
            putValue(Action.SHORT_DESCRIPTION,
                    NbBundle.getMessage(ActionsFactory.class, "HINT_EditTableCell"));
            this.location = location;
            this.table = table;
        }

        public void actionPerformed(ActionEvent e) {
            table.editCellAt(table.rowAtPoint(location), table.columnAtPoint(location));
            Component editComp = table.getEditorComponent();
            if (editComp != null) {
                editComp.requestFocusInWindow();
            }
        }
        
    }

}
